# encoding: utf-8
import re, requests, pexpect, datetime, time

from app.extensions import log, db
from flask_restplus import Resource
from app.extensions.api import Namespace
from app.modules.common.utils import DbCommit, checkToken, recursive_json_loads, strToList, sendMqMessage
from app.modules.common.schemas import BaseSchema
from . import parameters, schemas
from .models import ProjectPlanInfo, PlanMember
from app.modules.project import schemas as projectschemas
from app.modules.project.models import ProjectInfo, ProjectOutBeetle
from app.modules.project.resources import addChangeRecord
from config import BaseConfig
from app.modules.project.util import checkSvnIsExist, getSvnLastBranchNnm, createProject, getProjectSvnUrl, getSvnTrunk, \
    projectIsMavenJar, getProjectSvnDirName, checkSvnVersionIsExist, _add1
from app.modules.thread.thread import AddThreadTask
from app.modules.common.beetleHttpUtil import beetlePost
from app.modules.operation.resources import addOperationRecord
from app.modules.project import TRUNK_LIST
from app.modules.integration.models import IntegrationPlan
from app.modules.integration.resources import getIntegrationProjectInfoList, getIntegrationMembers
from app.modules.project.common import getProByParam, getProListByParam, getProListByParamWithCache
from .common import getPlanByParam, getPlanListByParamWithCache
from app.modules.zentao.resources import ZenTao, saveZentaoPlanMembers
from app.modules.common.dateUtil import getIntegrationCycleByDate
from app.modules.common.mailUtil import sendMail

api = Namespace('project_plan', description = "项目计划")  # pylint: disable=invalid-name


@api.route('/checkProjectPlanCode')
class CheckProjectPlanCode(Resource):
    @api.parameters(parameters.CheckProjectPlanCodeParameters())
    @api.response(schemas.CheckProjectPlanCodeSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        检查项目代号
        :param args:
        :return:
        """
        log.info("============== start check project plan code ==============")
        _plan_code = args['plan_code']
        _project_names = eval(args['project_names'])
        log.info("============== plan_code is %s ==============" % _plan_code)
        log.info("============== project_names is %s ==============" % _project_names)
        if type(_project_names) is not list:
            _msg = "this project_names %s is not list" % _project_names
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        return _checkPlanCode(_plan_code, _project_names)


@api.route('/getPlanProjectDefaultName')
class GetPlanProjectDefaultName(Resource):
    @api.parameters(parameters.GetPlanProjectDefaultNameParameters())
    @api.response(projectschemas.ProjectListSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        根据项目关联工程列表获取默认来源及目标分支、分支版本号
        :param args:
        :return:
        """
        log.info("============== start get project from branch and default name and num ==============")
        _plan_code = args['plan_code']
        _trunk_list = args['trunk_list']
        log.info("============== plan_code is %s ==============" % _plan_code)
        log.info("============== trunk_list is %s ==============" % _trunk_list)
        _trunk_list = eval(_trunk_list)
        if type(_trunk_list) is not list:
            _msg = "this trunk_list %s is not list" % _trunk_list
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        _list = []
        for _trunk in _trunk_list:
            _trunk_name = _trunk

            _obj = getSvnLastBranchNnm(_trunk_name)
            _branch_num = _obj['branch_num']

            _name = '%s-%s.%s' % (_trunk_name, _branch_num, _plan_code)

            _info = ProjectInfo(vcs_path = _name, trunk_name = _trunk_name,
                                svn_from = getProjectSvnDirName(trunkName = _trunk_name, withTrunk = True),
                                branch_num = _branch_num)
            _info.svn_list = _obj['list']
            _list.append(_info)

        return {'success': True, 'msg': '', 'data': _list}


@api.route('/getProjectVcsUrlList')
class GetProjectVcsUrlList(Resource):
    @api.parameters(parameters.GetProjectVcsUrlListParameters())
    @api.response(schemas.TrunkVcsUrlListSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        根据工程名称获取工程vcs url 地址列表
        :param args:
        :return:
        """
        log.info("============== start get project vcs url list ==============")
        _trunk_name = args['trunk_name']
        log.info("============== trunk_name is %s ==============" % _trunk_name)
        _svn = getProjectSvnUrl(trunkName = _trunk_name, withBranch = True)
        _list = []
        _sh = '/bin/bash -c "svn list %s"' % _svn
        output, status = pexpect.run(_sh, withexitstatus = 1, timeout = 60)
        if status == 0:
            _list.append({'url': getProjectSvnUrl(trunkName = _trunk_name, withTrunk = True), 'name': 'trunk'})
            _l = str(output).split('\r\n')
            for _name in _l:
                if _name != '':
                    _name = str(_name).replace('/', '')
                    _list.append({'url': '%s/%s' % (_svn, _name), 'name': _name})
            return {'success': True, 'msg': '', 'data': _list}
        else:
            _msg = "查询工程svn地址列表异常. %s" % output
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}


@api.route('/createProjectPlan')
class CreateProjectPlan(Resource):
    @api.parameters(parameters.CreateProjectPlanParameters())
    @api.response(schemas.PlanInfoSchema(many = False))
    @checkToken
    def post(self, args, **kwargs):
        """
        创建项目计划
        :param args:
        :return:
        """
        log.info("============== start create a project plan ==============")
        _plan_code = args['plan_code']
        _plan_name = str(args['plan_name']).replace(' ', '')
        _zt_project_id = args['zt_project_id']
        _ucloud = args['ucloud']
        _recover_date = args['recover_date']
        _start_date = args['start_date']
        _end_date = args['end_date']
        _integration_date = args['integration_date']
        _online_date = args['online_date']
        _pm = args['pm']
        _pm_name = args['pm_name']
        _zones = args['zones']
        _teamMembers = args['teamMembers']
        _projects = args['projects']
        _isUseWeb = args['isUseWeb']
        _useWebDesc = args['useWebDesc']
        _useOldEnv = args['useOldEnv']

        _plan = getPlanByParam(plan_name = _plan_name, withCache = True)
        # 检查项目名称及代号是否存在
        if _plan is not None:
            _msg = "项目 %s 已存在" % _plan_name
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _plan = getPlanByParam(plan_code = _plan_code, withCache = True)
        if _plan is not None:
            _msg = "项目代号 %s 已存在" % _plan_code
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _project_list = []
        if _projects is not None:
            _project_list = strToList(_projects)
            if type(_project_list) is not list:
                _msg = "this projects %s is not list" % _projects
                log.error("============== %s ==============" % _msg)
                return {'success': False, 'msg': _msg}

        _teamMembers = eval(_teamMembers)
        if type(_teamMembers) is not list:
            _msg = "this teamMembers %s is not list" % _teamMembers
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        if _ucloud is not None:
            _ucloud = eval(_ucloud)
            if type(_ucloud) is not list:
                _msg = "this ucloud %s is not list" % _ucloud
                log.error("============== %s ==============" % _msg)
                return {'success': False, 'msg': _msg}

        return createProjectPlan(_plan_name = _plan_name, _plan_code = _plan_code,
                                 _integration_date = _integration_date, _online_date = _online_date,
                                 _zt_project_id = _zt_project_id, _start_date = _start_date, _end_date = _end_date,
                                 _pm = _pm, _pm_name = _pm_name, _project_list = _project_list,
                                 _teamMembers = _teamMembers, _ucloud = _ucloud, _recover_date = _recover_date,
                                 _token_id = args['token_id'], _zones = _zones, _isUseWeb = _isUseWeb,
                                 _useWebDesc = _useWebDesc, _useOldEnv = _useOldEnv, **kwargs)


@api.route('/createEmergencyProjectPlan')
class CreateEmergencyProjectPlan(Resource):
    @api.parameters(parameters.CreateEmergencyProjectPlanParameters())
    @api.response(schemas.PlanInfoSchema(many = False))
    @checkToken
    def post(self, args, **kwargs):
        """
        创建紧急项目
        :param args:
        :return:
        """
        log.info("============== start create a  emergency project plan ==============")
        _plan_code = args['plan_code']
        _plan_name = str(args['plan_name']).replace(' ', '')
        _ucloud = args['ucloud']
        _online_date = args['online_date']
        _zones = args['zones']
        _projects = args['projects']
        _isUseWeb = args['isUseWeb']
        _useWebDesc = args['useWebDesc']
        _recover_date = args['recover_date']
        _bug_num = args['bug_num']
        _need_num = args['need_num']
        _test_head = args['test_head']
        _test_head_name = args['test_head_name']

        if _bug_num is None and _need_num is None:
            _msg = "bug号和需求号不能同时为空"
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _plan = getPlanByParam(plan_name = _plan_name, withCache = True)
        # 检查项目名称及代号是否存在
        if _plan is not None:
            _msg = "项目 %s 已存在" % _plan_name
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _plan = getPlanByParam(plan_code = _plan_code, withCache = True)
        if _plan is not None:
            _msg = "项目代号 %s 已存在" % _plan_code
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _project_list = []
        if _projects is not None:
            _project_list = strToList(_projects)
            if type(_project_list) is not list:
                _msg = "this projects %s is not list" % _projects
                log.error("============== %s ==============" % _msg)
                return {'success': False, 'msg': _msg}

        if _ucloud is not None:
            _ucloud = eval(_ucloud)
            if type(_ucloud) is not list:
                _msg = "this ucloud %s is not list" % _ucloud
                log.error("============== %s ==============" % _msg)
                return {'success': False, 'msg': _msg}

        return createProjectPlan(_plan_name = _plan_name, _plan_code = _plan_code, _online_date = _online_date,
                                 _start_date = datetime.datetime.now(), _pm = kwargs['login_user_account'],
                                 _pm_name = kwargs['login_user_realname'], _project_list = _project_list,
                                 _ucloud = _ucloud, _recover_date = _recover_date, _token_id = args['token_id'],
                                 _zones = _zones, _isUseWeb = _isUseWeb, _useWebDesc = _useWebDesc, _isEmergency = '0',
                                 bug_num = _bug_num, need_num = _need_num, _test_head = _test_head,
                                 _test_head_name = _test_head_name, **kwargs)


def createProjectPlan(_plan_name = None, _plan_code = None, _integration_date = None, _online_date = None,
                      _zt_project_id = None, _start_date = None, _end_date = None, _pm = None, _pm_name = None,
                      _project_list = [], _teamMembers = [], _ucloud = [], _recover_date = None, _token_id = None,
                      _zones = None, _isUseWeb = None, _useWebDesc = None, _isEmergency = '1', bug_num = None,
                      need_num = None, _test_head = None, _test_head_name = None, _useOldEnv = None, **kwargs):
    _info = ProjectPlanInfo(plan_name = _plan_name, plan_code = _plan_code, expect_integration_date = _integration_date,
                            expect_release_date = _online_date, zt_id = _zt_project_id, start_date = _start_date,
                            end_date = _end_date, PM = _pm, PM_name = _pm_name, plan_status = 'PLAN_NEW',
                            isEmergency = _isEmergency, bug_num = bug_num, need_num = need_num, test_head = _test_head,
                            test_head_name = _test_head_name)

    addOperationRecord('ADD_PROJECT_PLAN', _info.plan_id, None, _plan_name, 'add project plan', **kwargs)

    # 禅道id不为空时 查询项目产品列表
    if _zt_project_id is not None:
        _info.link_product = ZenTao.ProjectProduct(_zt_project_id)

    db.session.add(_info)

    _error_desc = ""

    _plan_projects = []
    # 校验工程来源是否存在、工程分支名称是否符合规则、工程是否已存在
    _jarlist = []
    for _pro in _project_list:
        if projectIsMavenJar(_pro['trunk_name']):
            _rtn = validateAndCreateProject(_pro['trunk_name'], _pro['svn_from'], _pro['vcs_path'],
                                            _pro['branch_num'], _info.plan_id, _plan_code)
            if not _rtn['success']:
                _error_desc = "%s%s;" % (_error_desc, _rtn['msg'])
                continue
            _jarlist.append(_rtn['data'])
            _plan_projects.append(_rtn['data'])
            db.session.add(_rtn['data'])

    for _pro in _project_list:
        # 不在beetle管理的工程  只在数据库中创建记录
        _proinfo = ProjectOutBeetle.query.filter_by(trunk_name = _pro['trunk_name']).first()
        if _proinfo is not None:
            db.session.add(
                ProjectInfo(project_name = "%s_%s.%s" % (_pro['trunk_name'], _pro['branch_num'], _plan_code),
                            plan_id = _info.plan_id, trunk_name = _pro['trunk_name'], vcs_path = _pro['vcs_path'],
                            svn_from = _pro['svn_from'], branch_num = _pro['branch_num'],
                            project_status = 'PRO_CREATED', isOnBeetle = '1',
                            package_type = TRUNK_LIST[_pro['trunk_name']]))
            continue

        if projectIsMavenJar(_pro['trunk_name']):
            continue

        _rtn = validateAndCreateProject(_pro['trunk_name'], _pro['svn_from'], _pro['vcs_path'], _pro['branch_num'],
                                        _info.plan_id, _plan_code)
        if not _rtn['success']:
            _error_desc = "%s%s;" % (_error_desc, _rtn['msg'])
            continue
        _plan_projects.append(_rtn['data'])
        db.session.add(_rtn['data'])

    for _member in _teamMembers:
        db.session.add(
            PlanMember(plan_id = _info.plan_id, account = _member['account'], realname = _member['realname']))

    # 发送请求申请agent
    if _ucloud is not None:
        _url = "%s/apply_record/add?token_id=%s&applicant_id=%s&applicant_name=%s&expect_recover_date=%s" \
               "&plan_id=%s&plan_name=%s&start_date=%s&desc=%s&isUseWeb=%s&useWebDesc=%s&useOldEnv=%s" % (
                   BaseConfig.UCLOUD_MASTER_URL, _token_id, kwargs['login_user_id'],
                   kwargs['login_user_name'], _recover_date, _info.plan_id, _plan_name, _start_date, _zones,
                   _isUseWeb, _useWebDesc, _useOldEnv)
        for _ty in _ucloud:
            _u = '%s&type=%s' % (_url, _ty)
            log.info(
                '创建项目%s ,申请ucloud,环境 %s,%s' % (_plan_name, _ty, recursive_json_loads(requests.get(url = _u).content)))

    if _error_desc != "":
        _info.error_desc = _error_desc[0:len(_error_desc) - 1]

    _result = DbCommit(cleanPro = True, cleanPlan = True)

    if not _result['success']:
        return _result
    for _pinfo in _plan_projects:
        AddThreadTask(createProject, _pinfo.trunk_name, _pinfo.project_name, _pinfo.vcs_path, _pinfo.svn_from,
                      _token_id, _info.plan_id, _plan_name, _info.plan_code, _jarlist)

    return {'success': True, 'msg': "", 'data': _info}


def validateAndCreateProject(_trunk_name, _svn_from, _vcs_path, _branch_num, _plan_id, _plan_code):
    if not checkSvnIsExist(_trunk_name, _svn_from):
        _msg = "工程 %s svn来源 %s 不存在" % (_trunk_name, _svn_from)
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}

    if checkSvnIsExist(_trunk_name, _vcs_path):
        _msg = "新建工程 %s svn地址 %s 已存在" % (_trunk_name, _vcs_path)
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}

    _old_branch_num = _branch_num
    # 校验工程的svn版本号是否存在
    while checkSvnVersionIsExist(_trunk_name, _branch_num):
        log.info('============== 新建工程 %s svn版本号 %s 已存在 ==============' % (_trunk_name, _branch_num))
        _branch_num = re.sub("(\\d+)(\\.)(\\d+)(\\.)(\\d+)", _add1, _branch_num)

    if not re.search("(^%s-%s\\.%s$)" % (_trunk_name, _old_branch_num, _plan_code), _vcs_path):
        _msg = "新建工程 %s svn地址 %s 格式错误" % (_trunk_name, _vcs_path)
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}

    # 校验工程名是否已存在  不存在时添加数据库记录  后台创建工程 创建成功后 修改状态
    _project_name = "%s_%s.%s" % (_trunk_name, _branch_num, _plan_code)
    _project = getProByParam(project_name = _project_name, withCache = True)
    if _project is None:
        _vcsPath = '%s/%s' % (getProjectSvnUrl(trunkName = _trunk_name, withBranch = True),
                              "%s-%s.%s" % (getSvnTrunk(_trunk_name), _branch_num, _plan_code))
        _project = ProjectInfo(project_name = _project_name, plan_id = _plan_id, trunk_name = _trunk_name,
                               vcs_path = _vcsPath, svn_from = _svn_from, branch_num = _branch_num,
                               project_status = 'PRO_NOT_CREATE', package_type = TRUNK_LIST[_trunk_name])

        return {'success': True, 'msg': '', 'data': _project}
    else:
        _msg = "工程 %s 已存在,请更换工程名称创建" % _project_name
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}


@api.route('/getPlanList')
class GetPlanList(Resource):
    @api.parameters(parameters.GetPlanListParameters())
    @api.response(schemas.PlanListSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        获取项目概要信息列表
        :param args:
        :return:
        """
        log.info("============== start query plan list ==============")
        _plan_id = args['plan_id']
        _start_date = args['start_date']
        _end_date = args['end_date']
        _isShowOnline = args['isShowOnline']
        _hasProList = args['hasProList']
        _withIntPro = args['withIntPro']
        _isEmergency = args['isEmergency']

        if _plan_id is None:
            _rtn = getPlanList(_plan_id, _start_date, _end_date, _isShowOnline, _isEmergency,
                               _hasProList, _withIntPro, _user_name = kwargs['login_user_name'])
        else:
            _rtn = getPlanList(str(_plan_id).split(','), _start_date, _end_date, _isShowOnline, _isEmergency,
                               _hasProList, _withIntPro, _user_name = kwargs['login_user_name'])
        return _rtn

    @api.parameters(parameters.GetPlanListParameters())
    @api.response(schemas.PlanListSchema(many = False))
    @checkToken
    def post(self, args, **kwargs):
        """
        获取项目概要信息列表
        :param args:
        :return:
        """
        log.info("============== start query plan list ==============")
        _plan_id = args['plan_id']
        _start_date = args['start_date']
        _end_date = args['end_date']
        _isShowOnline = args['isShowOnline']
        _hasProList = args['hasProList']
        _withIntPro = args['withIntPro']
        _isEmergency = args['isEmergency']

        if _plan_id is None:
            _rtn = getPlanList(_plan_id, _start_date, _end_date, _isShowOnline, _isEmergency,
                               _hasProList, _withIntPro, _user_name = None)
        else:
            _rtn = getPlanList(str(_plan_id).split(','), _start_date, _end_date, _isShowOnline, _isEmergency,
                               _hasProList, _withIntPro, _user_name = None)
        return _rtn


def getPlanList(_plan_id, _start_date, _end_date, _isShowOnline, _isEmergency, _hasProList, _withIntPro,
                _user_name = None):
    _list = getPlanListByParamWithCache(plan_ids = _plan_id, start_date = _start_date, end_date = _end_date,
                                        isShowOnline = _isShowOnline, isEmergency = _isEmergency, with_member = True)
    for _p in _list:
        _p.notCreateProCount = len(getProListByParamWithCache(plan_id = _p.plan_id, project_status = 'PRO_NOT_CREATE'))
        if _hasProList:
            _p.projects = getPlanProList(_p.plan_id, _withIntPro = _withIntPro)

    return {'success': True, 'msg': '', 'data': _list}


@api.route('/delPlan')
class DelPlan(Resource):
    @api.parameters(parameters.ProjectPlanDelParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        删除项目
        :param args:
        :return:
        """
        log.info("============== start del plan ==============")
        _plan_id = args['plan_id']
        _type = args['type']
        _is_del_svn = args['is_del_svn']

        _info = getPlanByParam(plan_id = _plan_id)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('DEL_PROJECT_PLAN', _plan_id, _type, None, 'del project plan', **kwargs)

        if _info.plan_status == 'INTEGRATIONING':
            _msg = "项目 %s 已经开始集成，不能删除" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        if _type == '1':
            _info.isRemove = '0'
        elif _type == '2':
            for _pro in getProListByParam(plan_id = _plan_id):
                if _pro.trunk_name != _pro.project_name:
                    removeProFromBeetle(_pro.trunk_name, _pro.project_name, args['token_id'], _is_del_svn = _is_del_svn,
                                        _vcs_path = _pro.vcs_path)
                db.session.delete(_pro)
            db.session.delete(_info)

            # 删除项目的agent申请记录
            _url = "%s/apply_record/removeRecord?token_id=%s&plan_id=%s" % (
                BaseConfig.UCLOUD_MASTER_URL, args['token_id'], _plan_id)
            recursive_json_loads(requests.get(url = _url).content)

        else:
            _msg = "参数type %s 错误" % _type
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        # 项目删除成功后发送消息
        sendMqMessage('uplan.exchange', {"type": "DEL_PLAN", "map": {"plan_id": _plan_id}})
        return DbCommit(cleanPro = True, cleanPlan = True)


@api.route('/getPlanProjectList')
class GetPlanProjectList(Resource):
    @api.parameters(parameters.GetPlanProjectListParameters())
    @api.response(projectschemas.ProjectListSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        获取项目的工程列表
        :param args:
        :return:
        """
        log.info("============== start get plan project list ==============")
        _plan_id = args['plan_id']
        _is_uc_release = args['is_uc_release']
        _withIntPro = args['withIntPro']
        _info = getPlanByParam(plan_id = _plan_id, withCache = True)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _list = getPlanProListForUc(_plan_id, _is_uc_release, _withIntPro = _withIntPro)
        return {'success': True, 'msg': '', 'data': _list}


def getPlanProListForUc(_plan_id, _is_uc_release, _withIntPro = False):
    if _is_uc_release is None or not _is_uc_release:
        _list = getPlanProList(_plan_id, _withIntPro)
    else:
        _integration_plan = IntegrationPlan.query.filter_by(plan_id = _plan_id).first()
        if _integration_plan is None:
            _list = getPlanProList(_plan_id, _withIntPro)
        else:
            _list = getIntegrationProjectInfoList(_integration_plan.integration_id, None, True)
    return _list


def getPlanProList(_plan_id, _withIntPro = False):
    _list = []
    _integration_plan = IntegrationPlan.query.filter_by(plan_id = _plan_id).first()
    for info in getProListByParamWithCache(plan_id = _plan_id):
        if _withIntPro and _integration_plan is not None and info.project_status == 'PRO_MERGED':
            _l = getIntegrationProjectInfoList(_integration_plan.integration_id, None, True,
                                               _trunk_name = info.trunk_name)
            if len(_l) != 0:
                _list.append(_l[0])
        _list.append(info)
    return _list


@api.route('/getPlanProjectEditionList')
class GetPlanProjectEditionList(Resource):
    @api.parameters(parameters.GetPlanProjectListParameters())
    @api.response(projectschemas.ProjectOnlineVersionListSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        agent发布,获取工程项目版本
        :param args:
        :return:
        """
        log.info("============== start get plan project edition list ==============")
        _plan_id = args['plan_id']
        _is_uc_release = args['is_uc_release']

        _info = getPlanByParam(plan_id = _plan_id, withCache = True)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _list = []
        for pro in getPlanProListForUc(_plan_id, _is_uc_release):
            _list.append(str(pro.project_name))

        _param = {
            'apps': str(_list),
            'type': 'plan'
        }
        _rtn = beetlePost(args['token_id'], '/uCloud/getProjectReleaseVersions', _param)
        if _rtn.code != 0:
            _msg = "agent发布,获取工程项目版本失败,error:%s" % _rtn.desc
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        return {'success': True, 'msg': '', 'data': _rtn.data}


@api.route('/delPlanProject')
class DelPlanProject(Resource):
    @api.parameters(parameters.DelPlanProjectParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        删除项目工程
        :param args:
        :return:
        """
        log.info("============== start del plan project ==============")
        _plan_id = args['plan_id']
        _trunk_name = args['trunk_name']
        _is_del_svn = args['is_del_svn']

        _plan = getPlanByParam(plan_id = _plan_id, withCache = True)
        if _plan is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        _pro = getProByParam(plan_id = _plan_id, trunk_name = _trunk_name)
        if _pro is None:
            _msg = "项目 %s 内没有工程 %s" % (_plan_id, _trunk_name)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('DEL_PLAN_PROJECT', _plan_id, _trunk_name, None, 'del plan project', **kwargs)

        removeProFromBeetle(_trunk_name, _pro.project_name, args['token_id'], _is_del_svn = _is_del_svn,
                            _vcs_path = _pro.vcs_path)
        db.session.delete(_pro)
        return DbCommit(cleanPro = True)


def removeProFromBeetle(_trunk_name, _project_name, _token_id, _is_del_svn = False, _vcs_path = None):
    if _trunk_name == _project_name:
        _msg = "主干工程 %s 不需要调用beetle后台删除" % _project_name
        log.info("============== %s ==============" % _msg)
        return {'success': True, 'msg': ""}

    _proinfo = ProjectOutBeetle.query.filter_by(trunk_name = _trunk_name).first()
    if _proinfo is None:
        _url = "%s/project/delProject?tokenId=%s&projectName=%s" % (
            BaseConfig.BEETLE_API_URL, _token_id, _project_name)
        _rtn = recursive_json_loads(requests.post(url = _url).content)
        log.info("============== 调用beetle删除工程 %s ==============" % _project_name)
        if _rtn.code != 0:
            _msg = "调用beetle删除工程 %s 失败,error:%s" % (_project_name, _rtn.desc)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        if _is_del_svn and _vcs_path is not None:
            _sh = 'svn　delete　%s -m "beetle删除作废分支"' % _vcs_path
            output, status = pexpect.run(_sh, withexitstatus = 1, timeout = 60)
            if status == 0:
                log.info('删除工程 %s svn成功，%s' % (_project_name, _vcs_path))
            else:
                log.error('删除工程 %s svn异常，%s.cause: %s.' % (_project_name, _vcs_path, output))

    return {'success': True, 'msg': ""}


@api.route('/addPlanProjectExist')
class AddPlanProjectExist(Resource):
    @api.parameters(parameters.AddPlanProjectExistParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        为项目添加已经在beetle内存在的工程
        :param args:
        :return:
        """
        log.info("============== start add plan exist project ==============")
        _plan_id = args['plan_id']
        _trunk_name = args['trunk_name']
        _project_name = args['project_name']

        _info = getPlanByParam(plan_id = _plan_id, withCache = True)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('ADD_PLAN_PROJECT', _plan_id, None, _project_name, 'add plan project exist', **kwargs)

        # 校验项目是否已经集成  集成计划中是否有相同主干工程
        _result = addPlanProValidate(_plan_id, _trunk_name)
        if not _result['success']:
            return _result

        if getProByParam(trunk_name = _trunk_name, plan_id = _plan_id, withCache = True) is not None:
            _msg = "项目 %s 内已经存在工程%s" % (_plan_id, _trunk_name)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        if getProByParam(project_name = _project_name, withCache = True) is not None:
            _msg = "工程%s已绑定项目" % _project_name
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _isOnBeetle = '0'
        if ProjectOutBeetle.query.filter_by(trunk_name = _trunk_name).first() is not None:
            _isOnBeetle = '1'

        # 请求beetle获取工程信息
        _param = {
            'projectName': _project_name
        }
        _rtn = beetlePost(args['token_id'], '/project/getProject', _param)
        if _rtn.code != 0:
            _msg = "调用beetle获取工程 %s 信息失败,error:%s" % (_project_name, _rtn.desc)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        db.session.add(
            ProjectInfo(project_name = _project_name, plan_id = _plan_id, trunk_name = _trunk_name,
                        vcs_path = _rtn.data.vcsPath, local_path = getLocalPath(_trunk_name, _project_name),
                        isOnBeetle = _isOnBeetle, project_status = 'PRO_CREATED',
                        package_type = TRUNK_LIST[_trunk_name], package_name = _trunk_name))

        return DbCommit(cleanPro = True)


def getLocalPath(_trunk_name, _project_name):
    return '/usr/local/Beetle/projects/%s/%s/' % (_trunk_name, _project_name)


@api.route('/addPlanProject')
class AddPlanProject(Resource):
    @api.parameters(parameters.AddPlanProjectParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        添加项目工程
        :param args:
        :return:
        """
        log.info("============== start add plan project ==============")
        _plan_id = args['plan_id']
        _trunk_name = args['trunk_name']
        _project_name = args['project_name']
        _branchNum = args['branchNum']
        _fromProject = args['fromProject']

        _info = getPlanByParam(plan_id = _plan_id, withCache = True)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('ADD_PLAN_PROJECT', _plan_id, None, _project_name, 'add plan project', **kwargs)

        # 校验项目是否已经集成  集成计划中是否有相同主干工程
        _result = addPlanProValidate(_plan_id, _trunk_name)
        if not _result['success']:
            return _result

        # 不在beetle管理的工程  只在数据库中创建记录
        _proinfo = ProjectOutBeetle.query.filter_by(trunk_name = _trunk_name).first()
        if _proinfo is not None:
            db.session.add(
                ProjectInfo(project_name = '%s_%s.%s' % (_trunk_name, _branchNum, _info.plan_code),
                            plan_id = _info.plan_id, trunk_name = _trunk_name, vcs_path = _project_name,
                            svn_from = _fromProject, branch_num = _branchNum, project_status = 'PRO_CREATED',
                            isOnBeetle = '1', package_type = TRUNK_LIST[_trunk_name]))
            return DbCommit(cleanPro = True)

        # 获取项目内的jar工程列表
        _jarlist = []
        for _jar in getProListByParamWithCache(plan_id = _plan_id, package_type = 'maven_jar'):
            _jarlist.append(ProjectInfo(project_name = _jar.project_name, trunk_name = _jar.trunk_name))

        _rtn = validateAndCreateProject(_trunk_name, _fromProject, _project_name, _branchNum, _plan_id,
                                        _info.plan_code)
        if not _rtn['success']:
            return _rtn
        db.session.add(_rtn['data'])
        _result = DbCommit(cleanPro = True)
        if not _result['success']:
            return _result
        AddThreadTask(createProject, _rtn['data'].trunk_name, _rtn['data'].project_name, _rtn['data'].vcs_path,
                      _rtn['data'].svn_from, args['token_id'], _info.plan_id, _info.plan_name, _info.plan_code,
                      _jarlist)

        return {'success': True, 'msg': ""}


def addPlanProValidate(_plan_id, _trunk_name):
    _ip = IntegrationPlan.query.filter_by(plan_id = _plan_id).first()
    # 未集成
    if _ip is None:
        return {'success': True, 'msg': ""}
    # 已集成
    for pro in getIntegrationProjectInfoList(_ip.integration_id, None, True):
        if pro.trunk_name == _trunk_name:
            _msg = "项目%s已经集成,集成计划%s内存在工程%s,不能重复添加" % (_plan_id, _ip.integration_id, _trunk_name)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': '项目已经集成,集成计划内存在工程%s,不能重复添加' % pro.project_name}
    return {'success': True, 'msg': ""}


@api.route('/updatePlanInfo')
class UpdatePlanInfo(Resource):
    @api.parameters(parameters.UpdatePlanInfoParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        更新项目描述信息
        :param args:
        :return:
        """
        log.info("============== start update plan info ==============")
        _plan_id = args['plan_id']
        _start_date = args['start_date']
        _end_date = args['end_date']
        _integration_date = args['integration_date']
        _release_date = args['release_date']
        _data = {}
        _info = getPlanByParam(plan_id = _plan_id)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        if _start_date:
            _info.start_date = _start_date
            _data['begin'] = _start_date
        if _end_date:
            _info.end_date = _end_date
            _data['end'] = _end_date
        if _integration_date:
            _info.expect_integration_date = _integration_date
        if _release_date:
            _info.expect_release_date = _release_date
        _plan_code = args['plan_code'] if 'plan_code' in args else None
        _plan_name = args['plan_name'] if 'plan_name' in args else None
        if _plan_code:
            _info.plan_code = _plan_code
            _data['code'] = _plan_code
        if _plan_name:
            _info.plan_name = _plan_name;
            _data['name'] = _plan_name
        ZenTao.UpdatePlanInfo(_info.zt_id, _data)
        return DbCommit(cleanPlan = True)


@api.route('/addPlanMembers')
class AddPlanMembers(Resource):
    @api.parameters(parameters.PlanMemberParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def post(self, args, **kwargs):
        """
        新增项目人员
        :param args:
        :return:
        """
        log.info("============== start add plan member ==============")
        _plan_id = args['plan_id']
        _list = strToList(args['list'])
        log.info("============== plan_id is %s ==============" % _plan_id)
        log.info("============== list is %s ==============" % _list)
        _info = getPlanByParam(plan_id = _plan_id, withCache = True)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        if type(_list) is not list:
            _msg = "this list %s is not list" % _list
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        return refreshPlanMembers(_plan_id, _list)


def refreshPlanMembers(_plan_id, _members_lst):
    _info = getPlanByParam(plan_id = _plan_id, withCache = True)
    _pm = None
    _pm_name = None
    _accounts = []
    db.session.execute("DELETE from plan_member where plan_id = :_plan_id", {'_plan_id': _plan_id})
    for _mem in _members_lst:
        _account = _mem['account']
        _accounts.append(_account)
        _realname = None
        if 'realname' in _mem:
            _realname = _mem['realname']
        _role = None
        if 'role' in _mem:
            _role = _mem['role']
        if _role == 'pm':
            _pm = _account
        _db_mem = None
        _db_mem_init = False
        if not _account:
            if not _db_mem_init:
                _db_mem = PlanMember.query.filter_by(plan_id = _plan_id, account = _account).first()
                _db_mem_init = True
            if _db_mem:
                _account = _db_mem.account
        if not _realname:
            if not _db_mem_init:
                _db_mem = PlanMember.query.filter_by(plan_id = _plan_id, account = _account).first()
                _db_mem_init = True
            if _db_mem:
                _realname = _db_mem.realname
        if not _role:
            if not _db_mem_init:
                _db_mem = PlanMember.query.filter_by(plan_id = _plan_id, account = _account).first()
                _db_mem_init = True
            if _db_mem:
                _role = _db_mem.role
            if not _role:
                _role = 'member'
        if _pm and _pm == _account:
            _pm_name = _realname
        _member = PlanMember(_info.plan_id, _account, _realname, _role)
        db.session.add(_member)
    saveZentaoPlanMembers(_info.zt_id, _accounts)
    if _pm:
        _info = getPlanByParam(plan_id = _plan_id)
        _info.PM = _pm
        _info.PM_name = _pm_name
    _db_rs = DbCommit(cleanPlan = True)
    _db_rs['data'] = ''
    return _db_rs


@api.route('/updatePlanPM')
class UpdatePlanPM(Resource):
    @api.parameters(parameters.PlanPMParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        更改项目负责人
        :param args:
        :return:
        """
        log.info("============== start update plan pm ==============")
        _plan_id = args['plan_id']
        _account = args['account']
        _realname = args['realname']
        log.info("============== plan_id is %s ==============" % _plan_id)
        log.info("============== account is %s ==============" % _account)
        log.info("============== realname is %s ==============" % _realname)
        _info = getPlanByParam(plan_id = _plan_id)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('UPDATE_PLAN_PM', '%s|PM' % _plan_id, _info.PM, _account, 'update plan pm', **kwargs)

        _info.PM = _account
        _info.PM_name = _realname

        return DbCommit(cleanPlan = True)


@api.route('/getUcloudApplyInfo')
class GetUcloudApplyInfo(Resource):
    @api.parameters(parameters.ProjectPlanBaseParameters())
    @api.response(schemas.ApplyInfoSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        获取项目ucloud申请信息
        :param args:
        :return:
        """
        log.info("============== start get ucloud apply info ==============")
        _plan_id = args['plan_id']
        log.info("============== plan_id is %s ==============" % _plan_id)
        _info = getPlanByParam(plan_id = _plan_id, withCache = True)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _url = "%s/apply_record/?token_id=%s&isOnlyOneMouth=false&plan_id=%s" % (
            BaseConfig.UCLOUD_MASTER_URL, args['token_id'], _plan_id)
        _rtn = recursive_json_loads(requests.get(url = _url).content)
        log.info("============== %s ==============" % _rtn)
        if not _rtn['success']:
            _msg = "查询虚拟机申请列表异常. %s " % _rtn['msg']
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        return {'success': True, 'msg': '', 'data': _rtn['data']}


@api.route('/getProjectListForPlans')
class GetProjectListForPlans(Resource):
    @api.parameters(parameters.ProjectPlansBaseParameters())
    @api.response(projectschemas.PlansProjectListSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        根据项目列表，获取所有相关工程列表(合并相同主干工程)
        :param args:
        :return:
        """
        log.info("============== start get project list for plans ==============")
        _plan_ids = args['plan_ids']
        log.info("============== plan_ids is %s ==============" % _plan_ids)
        _plan_ids = eval(_plan_ids)
        if type(_plan_ids) is not list:
            _msg = "this plan_ids %s is not list" % _plan_ids
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _plans = getPlanListByParamWithCache(plan_ids = _plan_ids)
        if len(_plans) == 0:
            _msg = "项目 %s 不存在" % _plan_ids
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _projects = getProListByParamWithCache(plan_ids = _plan_ids)
        _list = []
        if len(_projects) == 0:
            return {'success': True, 'msg': '', 'data': _list}

        _trunks = []
        for _project in _projects:
            if _project.project_status != 'PRO_MERGED':
                if _project.trunk_name not in _trunks:
                    _trunks.append(_project.trunk_name)
                    _list.append(_project)

        for _p in _list:
            _l = []
            for _project in _projects:
                if _project.trunk_name == _p.trunk_name:
                    _l.append(_project)
            if len(_l) > 0:
                _p.list = _l

        return {'success': True, 'msg': '', 'data': _list}


def _checkPlanCode(_plan_code, _project_names):
    _list = getPlanListByParamWithCache(plan_code = _plan_code)
    if len(_list) != 0:
        _msg = "项目代号 %s 已存在" % _plan_code
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}

    _errList = []

    for _pro in _project_names:

        _info = ProjectOutBeetle.query.filter_by(trunk_name = _pro).first()

        if _info is None:
            _svn = getProjectSvnUrl(trunkName = _pro, withBranch = True)
            _sh = '/bin/bash -c "svn list %s"' % _svn
            output, status = pexpect.run(_sh, withexitstatus = 1, timeout = 60)
            if status == 0:
                _s = str(output).strip().replace('\r\n', '')
                if _plan_code in _s:
                    _msg = '项目代号已存在,请重新输入.pro:%s' % _pro
                    log.info('============== %s ==============' % _msg)
                    _errList.append({'name': _pro, 'msg': '项目代号已存在'})
            else:
                _msg = '查询svn目录异常, cause: %s.' % output
                log.info('============== %s ==============' % _msg)
                _errList.append({'name': _pro, 'msg': '查询svn目录异常'})

    if len(_errList) == 0:
        return {'success': True, 'msg': ''}
    else:
        return {'success': False, 'msg': '', 'data': _errList}


@api.route('/toIntegrationPlan')
class ToIntegrationPlan(Resource):
    @api.parameters(parameters.ProjectPlanBaseParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        项目转测
        :param args:
        :return:
        """
        log.info("============== start to integration plan ==============")
        _plan_id = args['plan_id']
        log.info("============== plan_id is %s ==============" % _plan_id)
        _plan = getPlanByParam(plan_id = _plan_id)

        if _plan is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('TO_INTEGRATION_PLAN', '%s|plan_status' % _plan_id, _plan.plan_status, 'TO_INTEGRATION',
                           'to integration plan', **kwargs)

        for _pro in getProListByParam(plan_id = _plan_id):
            _pro.project_status = 'TO_INTEGRATION'
        _plan.plan_status = 'TO_INTEGRATION'

        return DbCommit(cleanPro = True, cleanPlan = True)


@api.route('/changePlanProjectBranch')
class ChangePlanProjectBranch(Resource):
    @api.parameters(parameters.ChangePlanProjectBranchParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        切换项目分支到beetle内已存在的分支工程
        :param args:
        :return:
        """
        log.info("============== start change plan project branch ==============")
        _plan_id = args['plan_id']
        _trunk_name = args['trunk_name']
        _project_name = args['project_name']

        _vaRtn = changePlanProgectValidate(_plan_id, _trunk_name, _project_name)

        if not _vaRtn['success']:
            return _vaRtn

        _oldPro = getProByParam(plan_id = _plan_id, trunk_name = _trunk_name)

        if _oldPro is None:
            _msg = "项目 %s 不存在工程%s" % (_plan_id, _trunk_name)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _param = {
            'projectName': _project_name,
        }
        _rtn = beetlePost(args['token_id'], '/project/getProject', _param)
        if _rtn.code != 0:
            _msg = "调用beetle查询工程 %s 信息失败,error:%s" % (_project_name, _rtn.desc)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        _integration_id = None
        _integration = IntegrationPlan.query.filter_by(plan_id = _plan_id).first()
        if _integration is not None:
            _integration_id = _integration.integration_id

        addOperationRecord('CHANGE_BRANCH', '%s|%s' % (_plan_id, _trunk_name), _oldPro.project_name,
                           _project_name, 'change plan project', **kwargs)

        addChangeRecord(_integration_id, _plan_id, _trunk_name, _oldPro.project_name, None,
                        _oldPro.project_status, _project_name, None, 'INTEGRATIONING',
                        'change_branch', **kwargs)
        # 切换时  删除旧分支记录
        db.session.delete(_oldPro)

        if _integration_id is None:  # 未进入集成
            db.session.add(ProjectInfo(project_name = _project_name, trunk_name = _trunk_name, plan_id = _plan_id,
                                       vcs_path = _rtn.data.vcsPath, project_status = 'PRO_DEVELOPING',
                                       package_type = TRUNK_LIST[_trunk_name]))
        else:
            db.session.add(ProjectInfo(project_name = _project_name, trunk_name = _trunk_name, plan_id = _plan_id,
                                       vcs_path = _rtn.data.vcsPath, project_status = 'INTEGRATIONING',
                                       package_type = TRUNK_LIST[_trunk_name]))
            # 将集成计划内其他项目的分支设置为被合并
            for _pro, _ip in db.session.query(ProjectInfo, IntegrationPlan).filter(
                    ProjectInfo.plan_id == IntegrationPlan.plan_id,
                    IntegrationPlan.integration_id == _integration_id,
                    ProjectInfo.trunk_name == _trunk_name, ProjectInfo.plan_id != _plan_id).all():
                _pro.project_status = 'PRO_MERGED'
                log.info('app.modules.projectPlan.resources.ChangePlanProjectBranch _pro=%s' % _pro.to_dict())
        return DbCommit(cleanPro = True)


def changePlanProgectValidate(_plan_id, _trunk_name, _project_name):
    if not str(_project_name).startswith(_trunk_name):
        _msg = "工程名错误.主干：%s 分支：%s" % (_trunk_name, _project_name)
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}

    _plan = getPlanByParam(plan_id = _plan_id, withCache = True)
    if _plan is None:
        _msg = "项目 %s 不存在" % _plan_id
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}

    _info = getProByParam(project_name = _project_name, withCache = True)

    if _info is not None:
        _p = getPlanByParam(plan_id = _info.plan_id, withCache = True)
        if _p is None:
            _msg = "工程 %s 已经被项目%s占用，请选择其他工程或解除占用" % (_project_name, _info.plan_id)
        else:
            _msg = "工程 %s 已经被项目%s占用，请选择其他工程或解除占用" % (_project_name, _p.plan_name)
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}

    if ProjectOutBeetle.query.filter_by(trunk_name = _trunk_name).first() is not None:
        _msg = "工程 %s 版本不受beetle控制" % _trunk_name
        log.error("============== %s ==============" % _msg)
        return {'success': False, 'msg': _msg}
    return {'success': True, 'msg': ''}


@api.route('/changePlanProjectToNewBranch')
class ChangePlanProjectToNewBranch(Resource):
    @api.parameters(parameters.ChangePlanProjectToNewBranchParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        切换项目内主干对应的分支,该分支工程为新工程
        :param args:
        :return:
        """
        log.info("============== start change plan project to new ==============")
        _plan_id = args['plan_id']
        _plan_code = args['plan_code']
        _plan_name = args['plan_name']
        _trunk_name = args['trunk_name']
        _project_name = args['project_name']
        _branchNum = args['branchNum']
        _fromProject = args['fromProject']

        _rtn = validateAndCreateProject(_trunk_name, _fromProject, _project_name, _branchNum, _plan_id, _plan_code)
        if not _rtn['success']:
            return _rtn

        _project_name = _rtn['data'].project_name

        _vaRtn = changePlanProgectValidate(_plan_id, _trunk_name, _project_name)

        if not _vaRtn['success']:
            return _vaRtn

        _oldPro = getProByParam(plan_id = _plan_id, trunk_name = _trunk_name)

        if _oldPro is None:
            _msg = "项目 %s 不存在工程%s" % (_plan_id, _trunk_name)
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('CHANGE_PLAN_PROJECT_TO_NEW', _plan_id, None, _project_name, 'change plan project to new',
                           **kwargs)

        _integration_id = None
        _integration = IntegrationPlan.query.filter_by(plan_id = _plan_id).first()
        if _integration is not None:
            _integration_id = _integration.integration_id

        addChangeRecord(_integration_id, _plan_id, _trunk_name, _oldPro.project_name, None,
                        _oldPro.project_status, _project_name, None, 'INTEGRATIONING',
                        'change_branch', **kwargs)

        # 获取项目内的jar工程列表
        _jarlist = []
        for _jar in getProListByParamWithCache(plan_id = _plan_id, package_type = 'maven_jar'):
            _jarlist.append(ProjectInfo(project_name = _jar.project_name, trunk_name = _jar.trunk_name))

        db.session.delete(_oldPro)
        db.session.add(_rtn['data'])

        if _integration_id is not None:
            # 将集成计划内其他项目的分支设置为被合并
            for _pro, _ip in db.session.query(ProjectInfo, IntegrationPlan).filter(
                    ProjectInfo.plan_id == IntegrationPlan.plan_id,
                    IntegrationPlan.integration_id == _integration_id,
                    ProjectInfo.trunk_name == _trunk_name, ProjectInfo.plan_id != _plan_id).all():
                _pro.project_status = 'PRO_MERGED'
                log.info('app.modules.projectPlan.resources.changePlanProjectToNewBranch _pro=%s' % _pro.to_dict())

        _result = DbCommit(cleanPro = True, cleanPlan = True)
        if not _result['success']:
            return _result
        AddThreadTask(createProject, _rtn['data'].trunk_name, _rtn['data'].project_name, _rtn['data'].vcs_path,
                      _rtn['data'].svn_from, args['token_id'], _plan_id, _plan_name, _plan_code,
                      _jarlist)

        return {'success': True, 'msg': ""}


@api.route('/updatePlanTestHead')
class UpdatePlanTestHead(Resource):
    @api.parameters(parameters.UpdatePlanTestHeadParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def post(self, args, **kwargs):
        """
        修改工程测试负责人
        :param args:
        :return:
        """
        _plan_id = args['plan_id']
        _test_head = args['test_head']
        _test_head_name = args['test_head_name']

        _info = getPlanByParam(plan_id = _plan_id)
        if _info is None:
            _msg = "项目 %s 不存在" % _plan_id
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        _info.test_head = _test_head
        _info.test_head_name = _test_head_name
        return DbCommit(cleanPlan = True)


@api.route('/getPlanUserWithRole')
class GetPlanUserWithRole(Resource):
    @api.parameters(parameters.ProjectPlanBaseParameters())
    @api.response(schemas.UserListSchema(many = False))
    # @checkToken
    def get(self, args, **kwargs):
        """
        获取项目的成员列表，包括成员的角色。供matrix用
        :param args:
        :return:
        """
        _plan_id = args['plan_id']
        log.info("============== plan_id is %s ==============" % _plan_id)

        _list = PlanMember.query.filter_by(plan_id = _plan_id).all()

        return {'success': True, 'msg': "", 'data': _list}


@api.route('/createPlanByPrd')
class CreatePlanByPrd(Resource):
    @api.parameters(parameters.CreateProjectPlanByMatrixParameters())
    @api.response(schemas.CreatePlanByPrd(many = False))
    @checkToken
    def post(self, args, **kwargs):
        """
        matrix prd创建项目计划
        :param args:
        :return:
        """
        log.info("============== start create a project plan ==============")
        _plan_code = args['plan_code']
        _plan_name = args['plan_name']
        _start_date = args['start_date']
        _end_date = args['end_date']
        _teamMembers = args['teamMembers']
        _desc = args['desc']

        _plan = getPlanByParam(plan_name = _plan_name, withCache = True)
        # 检查项目名称及代号是否存在
        if _plan is not None:
            _msg = "项目 %s 已存在" % _plan_name
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg, 'data': ''}

        _plan = getPlanByParam(plan_code = _plan_code, withCache = True)
        if _plan is not None:
            _msg = "项目代号 %s 已存在" % _plan_code
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg, 'data': ''}
        if _teamMembers:
            _teamMembers = eval(_teamMembers)
            if type(_teamMembers) is not list:
                _msg = "this teamMembers %s is not list" % _teamMembers
                log.error("============== %s ==============" % _msg)
                return {'success': False, 'msg': _msg, 'data': ''}
        return createProjectPlanByPrd(_plan_name = _plan_name, _plan_code = _plan_code,
                                      _start_date = _start_date, _end_date = _end_date,
                                      _teamMembers = _teamMembers,
                                      _token_id = args['token_id'], _desc = _desc, **kwargs)


def createProjectPlanByPrd(_plan_name = None, _plan_code = None,
                           _start_date = None, _end_date = None,
                           _teamMembers = [], _token_id = None,
                           _isEmergency = '1', _desc = None, **kwargs):
    _computeDate = getIntegrationCycleByDate(_end_date)
    _pm = None
    _pm_name = None
    if _teamMembers and len(_teamMembers) > 0:
        for (account, realname, role) in _teamMembers:
            if 'pm' == role:
                _pm = account
                _plan_name = realname
                break
    beetlePlanCreateParams = {}
    beetlePlanCreateParams['name'] = _plan_name
    beetlePlanCreateParams['code'] = _plan_code
    beetlePlanCreateParams['begin'] = _start_date
    beetlePlanCreateParams['end'] = _end_date
    beetlePlanCreateParams['acl'] = 'open'
    if _desc:
        beetlePlanCreateParams['desc'] = _desc
    _createPlanRs = ZenTao.CreatePlan(beetlePlanCreateParams)
    _zt_project_id = re.sub("([\\S\\s]*)(projectID=)([0-9]+)([\\S\\s]*)", "\\3", _createPlanRs)
    # re.compile("[0-9]+").match(_zt_project_id)
    if not _zt_project_id:
        log.error(_createPlanRs)
        return {'success': True, 'msg': "创建禅道项目失败"}
    _info = ProjectPlanInfo(plan_name = _plan_name, plan_code = _plan_code,
                            expect_integration_date = _computeDate['start'],
                            expect_release_date = _computeDate['end'], zt_id = _zt_project_id, start_date = _start_date,
                            end_date = _end_date, PM = _pm, PM_name = _pm_name, plan_status = 'PLAN_NEW',
                            isEmergency = _isEmergency)

    addOperationRecord('ADD_PROJECT_PLAN', _info.plan_id, None, _plan_name, 'add project plan', **kwargs)

    # 查询项目产品列表
    # _info.link_product = ZenTao.ProjectProduct(_zt_project_id)

    db.session.add(_info)

    # _error_desc = ""

    # _plan_projects = []

    if _teamMembers:
        # for _member in _teamMembers:
        #     db.session.add(
        #         PlanMember(plan_id = _info.plan_id, account = _member['account'], realname = _member['realname'],
        #                    role = _member['role']))
        refreshPlanMembers(_info.plan_id, _teamMembers)
    # if _error_desc != "":
    #     _info.error_desc = _error_desc[0:len(_error_desc) - 1]

    _result = DbCommit(cleanPro = True, cleanPlan = True)

    if not _result['success']:
        _result['data'] = ''
        return _result
    return {'success': True, 'msg': "", 'data': _info.plan_id}


@api.route('/checkPlanCode')
class CheckPlanCode(Resource):
    @api.parameters(parameters.CheckPlanCodeParameters())
    @api.response(schemas.BaseSchema(many = False))
    # @checkToken
    def get(self, args, **kwargs):
        """
         校验数据库中是否存在相同的项目代号
        :param args:
        :return:
        """
        _plan_id = args['plan_id']
        log.info("============== plan_id is %s ==============" % _plan_id)
        _plan_code = args['plan_code']
        log.info("============== plan_code is %s ==============" % _plan_code)

        _project_plan = getPlanByParam(plan_code = _plan_code)
        if _project_plan and _plan_id != _project_plan.plan_id:
            return {'success': False, 'msg': "plan_code冲突"}
        return {'success': True, 'msg': ""}


@api.route('/addPlanMemberss')
class AddPlanMemberss(Resource):
    @api.parameters(parameters.CreateProjectPlanByMatrixParameters())
    @api.response(BaseSchema(many = False))
    def post(self, args, **kwargs):
        members = ['yuelin.wang', 'chong.zou', 'jian.wang']
        # <<<<<<< Updated upstream
        # saveZentaoPlanMembers(1758, members)
        # _data = {'PM':'jian.wang'}
        # ZenTao.UpdatePlanInfo(1758,_data)
        ZenTao.DeletePlanMembers(1758, 'jian.wang')
        # =======
        saveZentaoPlanMembers(1758, members)
        _data = {'PM': 'jian.wang'}
        ZenTao.UpdatePlanInfo(1758, _data)
        # >>>>>>> Stashed changes
        return {'success': True, 'msg': ""}


@api.route('/sendMqMsg')
class sendMqMsg(Resource):
    @api.parameters(parameters.MessageParameters())
    @api.response(BaseSchema(many = False))
    def get(self, args, **kwargs):
        _exchange = args['exchange']
        _type = args['type']
        _plan_id = args['plan_id']
        log.info("============== sendMqMsg exchange is %s ==============" % _exchange)
        log.info("============== sendMqMsg type is %s ==============" % _type)
        log.info("============== sendMqMsg plan_id is %s ==============" % _plan_id)
        sendMqMessage(_exchange, {"type": _type, "map": {"plan_id": _plan_id}})
        return {'success': True, 'msg': ""}


def getEndPlanList(_day = 7):
    """
    查询结束时间还剩7天内的项目列表
    :return:
    """
    now = datetime.datetime.now()
    _time = now + datetime.timedelta(days = _day)
    _list = ProjectPlanInfo.query.filter(ProjectPlanInfo.end_date <= _time).filter(
        ProjectPlanInfo.plan_status != 'ONLINE').all()
    log.info('app.modules.projectPlan.resources.getEndPlanList list size is %s' % len(_list))
    return _list


def notifyPM(_plans):
    """
    项目快到期，邮件通知项目负责人
    :return:
    """
    # _list = getEndPlanList()
    for _plan in _plans:
        _to = ''
        log.info('app.modules.projectPlan.resources.notifyPM . plan id is %s . plan name is %s ' % (
            _plan.plan_id, _plan.plan_name))

        for _me in PlanMember.query.filter_by(plan_id = _plan.plan_id).all():
            _to = '%s%s@winit.com.cn,' % (_to, _me.account)
        if _to != '':
            log.info('app.modules.projectPlan.resources.notifyPM . send mail . plan id is %s . plan name is %s ' % (
                _plan.plan_id, _plan.plan_name))
            # 发邮件通知
            _subject = '项目[%s]到期通知' % _plan.plan_name
            _body = '项目[%s]将在[%s]到期,项目到期后ucloud自动回收.如果需要继续使用,请前往禅道修改项目时间!' % (_plan.plan_name, _plan.end_date)
            sendMail(_to = '%s,lunan.xu@winit.com.cn,tongsan.chen@winit.com.cn,min.wan@winit.com.cn' % _to,
                     _subject = _subject, _body = _body)


@api.route('/dayNotify')
class dayNotify(Resource):
    @api.parameters(parameters.DayNotifyParameters())
    @api.response(BaseSchema(many = False))
    def get(self, args, **kwargs):
        """
        发邮件通知还有x天到期的项目的负责人
        :param args:
        :param kwargs:
        :return:
        """
        _day = args['day']
        log.info('app.modules.projectPlan.resources.dayNotify start . day is %s' % _day)
        if _day is None:
            _msg = "day is none"
            log.error("============== %s ==============" % _msg)
            return {'success': False, 'msg': _msg}
        _list = getEndPlanList(_day = _day)
        notifyPM(_plans = _list)
        log.info('app.modules.projectPlan.resources.dayNotify end')
        return {'success': True, 'msg': ""}
